package com.cc.spring.utils;

import com.cc.spring.domain.User;
import com.cc.spring.exception.BusinessException;
import com.cc.spring.exception.BusinessExceptionCode;
import com.cc.spring.request.UserRequest;
import com.cc.spring.resp.HdfsFileStatus;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FSDataInputStream;
import org.apache.hadoop.fs.FileStatus;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.core.io.InputStreamResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Repository;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.web.bind.annotation.ResponseBody;

import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;

//    文件操作的工具类

@Component
public class HdfsUtil {
    private static final Logger LOG = LoggerFactory.getLogger(HdfsUtil.class);
    private FileSystem fs;
//    这样写的话直接在构造函数中是获取不到的，因为依赖注入的时机是在构造函数之后
//    @Value("${hdfs.url}")
//    private String HDFSPath;
//    初始化
//    直接在构造函数中获配置是刻意得
    public HdfsUtil( @Value("${hdfs.url}") String HDFSPath, @Value("${hdfs.user}") String user){
        try {
            URI uri = new URI(HDFSPath);
//            配置文件
            Configuration configuration = new Configuration();
            configuration.set("dfs.replication","2");
            fs = FileSystem.get(uri,configuration,user);
        } catch (URISyntaxException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    /**
     *
     * @param path 本地暂存文件的位置
     * @param fileName hdfs中文件名字
     * @param userPath hdfs中的目录
     * @return 是否成功
     */
//    上传文件
    public boolean upload(Path path, String fileName, String userPath){
        Path hdfsPATH = new Path("/" + userPath + "/" + fileName);
        try {
//            这个表示将本地path的文件，上传到HDFS的path1路径
//            这个方法是复制本地的文件
//            fs.copyFromLocalFile(false,false,path, hdfsPATH);
//            这个方法将本地文件，复制到hdfs中，那么就不需要定时任务了
            fs.moveFromLocalFile(path,hdfsPATH);
            LOG.info("上传成功,文件路径:{}", hdfsPATH);
            return true;
        } catch (IOException e) {
            e.printStackTrace();
//            失败
            return false;
        }
    }

    /***
     *
     * @param userName 用户根路径
     * @param path      全路径
     * @return          去掉用户根路径路径
     *  例如： userName = cc; path =  hdfs://hadoop:8020/cc/test/1.jpg
     *  返回：/test/1.jpg
     */
//    获取文件名
    public String getFileNamePath(String userName, Path path){
        String fileAllPath = String.valueOf(path);// 将path转换为字符串
//        System.out.println(path);
        return fileAllPath.substring(fileAllPath.indexOf(userName) + userName.length());
    }

    /**
     *
     * @param userReq 根据用户的name找到对应根目录
     * @return 返回用户目录的所有文件及目录信息
     */
//    展示指定目录的文件及文件夹
    public List<HdfsFileStatus> queryFileByUser(UserRequest userReq){
        Path path;
//        为空说明没有这个目录
        if(ObjectUtils.isEmpty(userReq.getFilePath())){
            path = new Path("/" + userReq.getUserName());
        }else {
//            目录存在就拼上这个目录，就是携带用户根目录
            path = new Path("/"+ userReq.getUserName() +"/"+userReq.getFilePath());
        }
//        文件状态
        FileStatus[] fileStatus;
        List<HdfsFileStatus> hdfsFileStatusList = new ArrayList<>();
        try {
//            当前目录下的所有信息
            fileStatus = fs.listStatus(path);
            for(FileStatus status : fileStatus){
                HdfsFileStatus hfs = new HdfsFileStatus();
//                如果是文件夹那么标识一下
                if(status.isDirectory()){
                    hfs.setDirectory(true);
                }
                //                获取文件名
                hfs.setFileName(status.getPath().getName());
//                System.out.println(status.getPath().getName());
//                文件路径，去除用户根
                hfs.setPath(getFileNamePath(userReq.getUserName(), status.getPath()));
//                获取文件实际大小
//                hfs.setFileSize(fs.getContentSummary(status.getPath()).getSpaceConsumed());
//                System.out.println(status.getLen());
                hfs.setFileSize(status.getLen());
                //                块的大小
//                hfs.setBlockSize(status.getBlockSize()/1000);
                hdfsFileStatusList.add(hfs);
            }
        } catch (IOException e) {
            e.printStackTrace();
//            不是文件
            hdfsFileStatusList.add(new HdfsFileStatus(false));
        }
        return hdfsFileStatusList;
    }
//    下载文件
    /**
     *
     * @param userName 用户名
     * @param paths 指定的路径地址文件
     * @return 返回一个响应体，携带相应的文件
     */
    public ResponseEntity<InputStreamResource> downFile(String userName, String paths){
//        拼接hdfs中的文件路径，/根路径，userName某个用户，paths具体的那个文件
        Path path = new Path("/" + userName + paths);
        LOG.info("文件路径：{}",String.valueOf(path));
//        需要判断文件是否存在，通过路径判断
        try {
//            文件不存在
            if(!fs.exists(path)){
                LOG.info("文件不存在");
                return null;
            }
        } catch (IOException e) {
            return null;
        }

        try {
            FSDataInputStream open = fs.open(path); // 打开一个文件
            ResponseEntity<InputStreamResource> inputStreamResourceResponseEntity = downloadFile(open, getFileName(path));
            return inputStreamResourceResponseEntity;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    public String getFileName(Path path) {
//        断言
        Assert.notNull(path, "");
//        先转换为字符串
        String file = String.valueOf(path);
//        返回最后一次/出现后的内容，就是文件名
        return file.substring(file.lastIndexOf("/") + 1);
    }

//    网络协议拼接，自定义方法,作用是返回一个数据
    /**
     *
     * @param inputStream
     * @param fileName
     * @return
     * @throws IOException
     */
    private ResponseEntity<InputStreamResource> downloadFile(FSDataInputStream inputStream, String fileName) throws IOException {
        Byte[] bytes = new Byte[inputStream.available()];
//        System.out.println(fileName);
        LOG.info("文件名：{}",fileName);
//        设置相应的相响应体内容前端才能够获取
        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.add("Cache-Control", "no-cache, no-store, must-revalidate");
        //通过设置头信息给文件命名，也即是，在前端，文件流被接受完还原成原文件的时候会以你传递的文件名来命名
        httpHeaders.add("Content-Disposition", String.format("attachment; filename=\"%s\"",
                URLEncoder.encode(fileName, "utf-8")));
        httpHeaders.add("Pragma", "no-cache");
        httpHeaders.add("Expires", "0");
        httpHeaders.add("Content-Language", "UTF-8");
//        System.out.println(httpHeaders);
        LOG.info("响应体信息：{}",httpHeaders);
        return ResponseEntity.ok().headers(httpHeaders).contentLength(bytes.length)
                .contentType(MediaType.parseMediaType("application/octet-stream;charset=UTF-8")).body(new InputStreamResource(inputStream));
    }

//    创建目录

    /**
     * 使用递归创建，多层目录也可以创建
     * @param userName 用户根路径
     * @param dirName 用户目录
     * @return 是否创建成功
     */
    public boolean mkdirs(String userName, String dirName){
        if(dirName.equals("") || dirName.equals("undefined")){
            throw new BusinessException(BusinessExceptionCode.DIR_NOT_NULL);
        }
        Path path1 = new Path("/" + userName + dirName);
        try {
            fs.mkdirs(path1);
            return true;
        } catch (IOException e) {
            e.printStackTrace();
            return false;
        }
    }

//    重载，创建用户的时候增加用户目录
    public boolean mkdirs(String userName){
        Path path1 = new Path("/" + userName );
        LOG.info("创建的目录：{}",String.valueOf(path1));
        try {
            fs.mkdirs(path1);
            return true;
        } catch (IOException e) {
            e.printStackTrace();
            return false;
        }
    }
//    删除文件
    public boolean deleteFile(String userName, String path){
        try {
            fs.delete(new Path("/"+userName+path),true);
            return true;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return false;
    }
    // 更改目录名

    /**
     *
     * @param oldUsername 最初的目录名
     * @param newUserName 新改的用户名
     * @return 是否成功
     */
    public  boolean renameDirByUserName (String oldUsername,String newUserName){
        Path oldPath = new Path("/" + oldUsername);
        Path newPath = new Path("/" + newUserName);
        LOG.info("原始目录：{}", oldPath);
        LOG.info("修改目录：{}", newPath);
        try {
//            成功返回
            return fs.rename(oldPath, newPath);
        } catch (IOException e) {
            e.printStackTrace();
            return false;
        }
    }
}
